using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;

namespace DynamicAppDomain
{
	public class CodeExecutor
	{
		public bool ImportAllNamespaces = true;
		public IEnumerable<string> Namespaces;
		public IEnumerable<string> References;
		public CodeExecutor() {}

		public CodeExecutor(params string[] refs)
		{
			References = refs;
		}

		static string EnsureDllExtension(string fileName)
		{
			if (!fileName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase))
			{
				fileName = fileName + ".dll";
			}
			return fileName;
		}

		public object Run(string code)
		{
			var options = new CompilerParameters { GenerateInMemory = true };
			foreach (var reference in typeof(CodeExecutor).Assembly.GetReferencedAssemblies())
			{
				options.ReferencedAssemblies.Add(EnsureDllExtension(reference.Name));
			}
			foreach (var reference in References)
			{
				options.ReferencedAssemblies.Add(EnsureDllExtension(reference));
			}

			var compiler = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v3.5" } });

			var compilerResults = compiler.CompileAssemblyFromSource(options, code);

			if (compilerResults.Errors.Count > 0)
			{
				var sb = new StringBuilder();
				foreach (var error in compilerResults.Errors)
				{
					sb.AppendLine(error.ToString());
				}
				sb.AppendLine(code);

				throw new Exception(sb.ToString());
			}

			var asm = compilerResults.CompiledAssembly;
			var programType = asm.GetType("Program", true, true);
			var methodMain = programType.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase);

			var result = methodMain.Invoke(null, new object[0]);
			return result;
		}

		public object RunExpression(string expression, params object[] args)
		{
			if (ImportAllNamespaces)
			{
				var namespaces = new List<string>();
				foreach (var reference in References)
				{
					var asm = Assembly.LoadFrom(EnsureDllExtension(reference));
					namespaces.AddRange(asm.GetTypes().Select(x => x.Namespace).Distinct());
				}
				if (Namespaces == null)
				{
					Namespaces = new string[0];
				}
				Namespaces = Namespaces.Union(namespaces).ToArray();
			}

			return Run(GetCodeForExpression(expression));
		}

		string GetCodeForExpression(string expression)
		{
			const string srcTemplate = @"
using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.IO;
using System.Text;
using Microsoft.CSharp;
{1}

#pragma warning disable 0028

static public class Program
{{
	static public object Main()
	{{
		return {0};
	}}
}}
";
			return string.Format(srcTemplate, expression, string.Join("", Namespaces.Select(x => "using " + x + ";\r\n").ToArray()));
		}
	}
}